package cs6238.common;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStream;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.crypto.Cipher;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PEMWriter;

/**
 * Dear BouncyCastle, 
 * 
 * You make my life easier.  I got this component working thanks to your excellent tools, 
 * documentation, and code samples.  You complete me.
 * 
 * Love,
 * @author Robert Parker
 * 
 * 
 * Seriously though, BouncyCastle made this component possible.  This is a command line
 * tool for generating a CA Cert, and then generating signed client certificates.
 */
public class CertificateHelper {
	
	public static final String ALIAS = "cs6238";

	
	public static void writeKeyPair(KeyPair pair, String filepath) throws Exception{
		PEMWriter pemWriter = new PEMWriter(new FileWriter(filepath));
		pemWriter.writeObject(pair);
		pemWriter.close();
	}
	
	public static KeyPair readKeyPair(String filepath) throws Exception{
		BufferedReader br = new BufferedReader(new FileReader(filepath));
		return (KeyPair) new PEMReader(br).readObject();
	}
	
	public static X509Certificate readCertificate(String filepath) throws Exception{
		InputStream inStream = new FileInputStream(filepath);
		CertificateFactory cf = CertificateFactory.getInstance("X.509");
		X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
		inStream.close();
		
		return cert;
	}
	
	public static void writeCertificate(X509Certificate cert, String filepath) throws Exception{
		FileOutputStream certificateOS = new FileOutputStream(new File(filepath));
		   certificateOS.write(cert.getEncoded());
		   certificateOS.close();
	}
	
	public static String getHostname(X509Certificate cert){
		Principal subject = cert.getSubjectDN();
		String parts[] = subject.getName().split(",");
		for(String part : parts){
			if(part.startsWith("CN=")){
				return part.substring(3);
			}
		}
		
		return null;
	}
		
	
	public static KeyStore readJKS(String filepath, String password) throws Exception{
		KeyStore keyStore = KeyStore.getInstance("JKS");
		keyStore.load(new FileInputStream(filepath), password.toCharArray());
		return keyStore;
	}
	
	public static byte[] signMessage(byte[] data, PrivateKey privateKey) throws Exception{
		Signature sig = Signature.getInstance("SHA1withRSA");
		sig.initSign(privateKey);
		sig.update(data);
		byte[] signed = sig.sign();
		return signed;
	}
	
	public static boolean verifyMessage(byte[] data, byte[] signature, PublicKey publicKey) throws Exception{
		Signature sig = Signature.getInstance("SHA1withRSA");
		sig.initVerify(publicKey);
		sig.update(data);
		return sig.verify(signature);
	}

	/**
	 * This padding thing is kinda bizarre.  Basically I am using a weird encryption mode: RSA/ECB/NoPadding
	 * The project needs deterministic RSA encryption as the result is being used as a key
	 * Normally RSA uses randomized padding and that was screwing up my result
	 * With this mode, all the zeros at the start of the message are left out, so I pad every message with a 1
	 * @param store
	 * @param hash
	 * @return
	 * @throws Exception
	 */
	public static byte[] encryptMessage(KeyStore store, byte[] hash) throws Exception{
		byte[] padded = new byte[hash.length + 1];
		padded[0] = 1;
		for(int i=0; i<hash.length; i++){
			padded[i+1] = hash[i];
		}
		
		Certificate cert = store.getCertificate(CertificateHelper.ALIAS);
		Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", "BC");
		cipher.init(Cipher.ENCRYPT_MODE, cert.getPublicKey());
		return cipher.doFinal(padded);	
	}

	public static byte[] decryptMessage(KeyStore store, String password, byte[] hash) throws Exception{
		Key key = store.getKey(CertificateHelper.ALIAS, password.toCharArray());
		Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", "BC");
		cipher.init(Cipher.DECRYPT_MODE, key);
		byte[] padded = cipher.doFinal(hash);
		
		byte[] result = new byte[padded.length - 1];
		for(int i=0; i< result.length; i++){
			result[i] = padded[i+1];
		}
		
		return result;
	}

	
}
